/**
 * Configuration Test Helpers
 * Utilities for creating isolated test environments for configuration testing
 */

const fs = require('fs');
const path = require('path');
const os = require('os');

class ConfigTestHelpers {
  /**
   * Create temporary configuration directory
   * @param {string} prefix - Directory name prefix
   * @returns {string} Temporary directory path
   */
  static createTempConfigDir(prefix = 'vision-analyzer-test') {
    const tempDir = path.join(os.tmpdir(), `${prefix}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`);

    if (!fs.existsSync(tempDir)) {
      fs.mkdirSync(tempDir, { recursive: true });
    }

    return tempDir;
  }

  /**
   * Create mock configuration files
   * @param {string} baseDir - Base directory for config files
   * @param {Object} configs - Configuration objects to create
   * @returns {Array} Created file paths
   */
  static createMockConfigFiles(baseDir, configs) {
    const createdFiles = [];

    for (const [filename, config] of Object.entries(configs)) {
      const filePath = path.join(baseDir, filename);

      try {
        fs.writeFileSync(filePath, JSON.stringify(config, null, 2));
        createdFiles.push(filePath);
      } catch (error) {
        console.warn(`Failed to create config file ${filename}:`, error.message);
      }
    }

    return createdFiles;
  }

  /**
   * Create mock configuration files with common patterns
   * @param {string} baseDir - Base directory
   * @param {Object} options - Options for file creation
   * @returns {Object} Created files information
   */
  static createCommonMockFiles(baseDir, options = {}) {
    const {
      createValid = true,
      createInvalid = true,
      createEmpty = true,
      createMalformed = true
    } = options;

    const configs = {};
    const createdFiles = {};

    // Create valid configuration
    if (createValid) {
      const validConfig = {
        version: '1.0.0',
        current_provider_model: 'test-test-model',
        providers: {
          test: {
            base: 'https://api.test.com',
            api_key: 'test_api_key',
            models: [
              {
                id: 'test-test-model',
                name: 'Test Model',
                type: 'vision',
                capabilities: ['image_analysis'],
                max_tokens: 4096,
                supports_vision: true,
                enabled: true
              }
            ]
          }
        }
      };

      const validPath = path.join(baseDir, 'settings.json');
      fs.writeFileSync(validPath, JSON.stringify(validConfig, null, 2));
      createdFiles.valid = validPath;
    }

    // Create invalid configuration (missing required fields)
    if (createInvalid) {
      const invalidConfig = {
        // Missing version, current_provider_model, providers
        http_server: {
          port: 3000
        }
      };

      const invalidPath = path.join(baseDir, 'settings-invalid.json');
      fs.writeFileSync(invalidPath, JSON.stringify(invalidConfig, null, 2));
      createdFiles.invalid = invalidPath;
    }

    // Create empty configuration
    if (createEmpty) {
      const emptyPath = path.join(baseDir, 'settings-empty.json');
      fs.writeFileSync(emptyPath, '{}');
      createdFiles.empty = emptyPath;
    }

    // Create malformed JSON
    if (createMalformed) {
      const malformedPath = path.join(baseDir, 'settings-malformed.json');
      fs.writeFileSync(malformedPath, '{ "invalid": json }');
      createdFiles.malformed = malformedPath;
    }

    return createdFiles;
  }

  /**
   * Clean up temporary files
   * @param {string|Array} paths - File paths to clean up
   * @param {boolean} recursive - Whether to clean directories recursively
   */
  static cleanupTempFiles(paths, recursive = true) {
    const pathsToClean = Array.isArray(paths) ? paths : [paths];

    for (const filePath of pathsToClean) {
      try {
        if (fs.existsSync(filePath)) {
          const stats = fs.statSync(filePath);

          if (stats.isDirectory() && recursive) {
            fs.rmSync(filePath, { recursive: true, force: true });
          } else if (stats.isFile()) {
            fs.unlinkSync(filePath);
          }
        }
      } catch (error) {
        console.warn(`Failed to cleanup ${filePath}:`, error.message);
      }
    }
  }

  /**
   * Create isolated test environment
   * @param {Object} options - Environment options
   * @returns {Object} Test environment object
   */
  static createIsolatedEnvironment(options = {}) {
    const {
      prefix = 'vision-analyzer-test',
      mockConfigs = null,
      cleanupOnDestroy = true
    } = options;

    const tempDir = this.createTempConfigDir(prefix);
    const createdFiles = mockConfigs ?
      this.createMockConfigFiles(tempDir, mockConfigs) :
      this.createCommonMockFiles(tempDir);

    return {
      tempDir,
      createdFiles,
      getConfigPath: (filename = 'settings.json') => path.join(tempDir, filename),
      exists: (filename) => fs.existsSync(path.join(tempDir, filename)),
      readConfig: (filename) => {
        const filePath = path.join(tempDir, filename);
        if (fs.existsSync(filePath)) {
          return JSON.parse(fs.readFileSync(filePath, 'utf8'));
        }
        return null;
      },
      writeConfig: (filename, config) => {
        const filePath = path.join(tempDir, filename);
        fs.writeFileSync(filePath, JSON.stringify(config, null, 2));
        return filePath;
      },
      cleanup: () => {
        if (cleanupOnDestroy) {
          this.cleanupTempFiles(tempDir);
        }
      }
    };
  }

  /**
   * Create mock file system for testing
   * @returns {Object} Mock file system object
   */
  static createMockFileSystem() {
    const files = new Map();
    const directories = new Set();

    return {
      existsSync: (filePath) => {
        return files.has(filePath) || directories.has(filePath);
      },

      readFileSync: (filePath, encoding) => {
        if (files.has(filePath)) {
          return files.get(filePath);
        }
        throw new Error(`ENOENT: no such file or directory, open '${filePath}'`);
      },

      writeFileSync: (filePath, content) => {
        files.set(filePath, content);
        // Ensure parent directories exist
        const dir = path.dirname(filePath);
        if (dir !== '.') {
          directories.add(dir);
        }
      },

      mkdirSync: (dirPath, options) => {
        directories.add(dirPath);
      },

      rmSync: (filePath, options) => {
        files.delete(filePath);
        directories.delete(filePath);

        if (options?.recursive) {
          // Remove all files/directories under this path
          for (const [path] of files) {
            if (path.startsWith(filePath)) {
              files.delete(path);
            }
          }
          for (const dir of directories) {
            if (dir.startsWith(filePath)) {
              directories.delete(dir);
            }
          }
        }
      },

      statSync: (filePath) => {
        if (files.has(filePath)) {
          return {
            isFile: () => true,
            isDirectory: () => false,
            size: files.get(filePath).length,
            mtime: new Date()
          };
        }
        if (directories.has(filePath)) {
          return {
            isFile: () => false,
            isDirectory: () => true,
            size: 0,
            mtime: new Date()
          };
        }
        throw new Error(`ENOENT: no such file or directory, stat '${filePath}'`);
      },

      // For debugging
      _files: files,
      _directories: directories,

      reset: () => {
        files.clear();
        directories.clear();
      }
    };
  }

  /**
   * Create configuration test scenarios
   * @returns {Object} Test scenarios
   */
  static createTestScenarios() {
    return {
      validMinimal: () => ({
        version: '1.0.0',
        current_provider_model: 'test-test-model',
        providers: {
          test: {
            base: 'https://api.test.com',
            api_key: 'test_api_key',
            models: [
              {
                id: 'test-test-model',
                name: 'Test Model',
                type: 'vision',
                capabilities: ['image_analysis'],
                max_tokens: 4096,
                supports_vision: true,
                enabled: true
              }
            ]
          }
        }
      }),

      validFull: () => ({
        version: '1.0.0',
        current_provider_model: 'test-test-model',
        providers: {
          test: {
            base: 'https://api.test.com',
            api_key: 'test_api_key',
            models: [
              {
                id: 'test-test-model',
                name: 'Test Model',
                type: 'vision',
                capabilities: ['image_analysis'],
                max_tokens: 4096,
                supports_vision: true,
                enabled: true
              }
            ],
            timeout: 30000,
            max_retries: 3
          }
        },
        http_server: {
          port: 3000,
          auto_start: false,
          host: 'localhost'
        },
        logging: {
          level: 'info',
          retention_days: 7,
          file_enabled: false,
          console_enabled: true
        }
      }),

      invalidMissingVersion: () => ({
        current_provider_model: 'test-test-model',
        providers: {
          test: {
            base: 'https://api.test.com',
            api_key: 'test_api_key',
            models: []
          }
        }
      }),

      invalidInvalidProvider: () => ({
        version: '1.0.0',
        current_provider_model: 'test-test-model',
        providers: {
          test: {
            base: 'invalid-url',
            api_key: 123, // Should be string
            models: 'invalid' // Should be array
          }
        }
      }),

      invalidNoProviders: () => ({
        version: '1.0.0',
        current_provider_model: 'test-test-model',
        providers: {}
      })
    };
  }

  /**
   * Assert configuration validation result
   * @param {Object} result - Validation result
   * @param {Object} expectations - Expected results
   */
  static assertValidationResult(result, expectations = {}) {
    const {
      success = true,
      hasErrors = false,
      hasWarnings = false,
      errorFields = [],
      warningCount = 0
    } = expectations;

    expect(result).toBeDefined();
    expect(result.success).toBe(success);

    if (hasErrors) {
      expect(result.errors).toBeDefined();
      expect(Array.isArray(result.errors)).toBe(true);
      expect(result.errors.length).toBeGreaterThan(0);

      if (errorFields.length > 0) {
        const actualFields = result.errors.map(e => e.field);
        for (const expectedField of errorFields) {
          expect(actualFields).toContain(expectedField);
        }
      }
    } else {
      expect(result.errors).toBeUndefined();
    }

    if (hasWarnings) {
      expect(result.warnings).toBeDefined();
      expect(Array.isArray(result.warnings)).toBe(true);
      expect(result.warnings.length).toBeGreaterThanOrEqual(warningCount);
    }
  }

  /**
   * Create Jest custom matchers for configuration testing
   * @returns {Object} Custom matchers
   */
  static createCustomMatchers() {
    return {
      toBeValidConfiguration(received) {
        const pass = received && received.success === true;

        if (pass) {
          return {
            message: () => `expected configuration not to be valid`,
            pass: true
          };
        } else {
          return {
            message: () => `expected configuration to be valid, but got errors: ${received?.errors?.map(e => e.message).join(', ') || 'unknown'}`,
            pass: false
          };
        }
      },

      toBeInvalidConfiguration(received, options = {}) {
        const pass = received && received.success === false;

        if (pass) {
          return {
            message: () => `expected configuration to be valid`,
            pass: true
          };
        } else {
          return {
            message: () => `expected configuration to be invalid, but it was valid`,
            pass: false
          };
        }
      },

      toHaveFieldError(received, fieldName) {
        const hasFieldError = received?.errors?.some(error =>
          error.field === fieldName || error.field?.endsWith(`.${fieldName}`)
        );

        if (hasFieldError) {
          return {
            message: () => `expected configuration not to have error for field '${fieldName}'`,
            pass: true
          };
        } else {
          return {
            message: () => `expected configuration to have error for field '${fieldName}'`,
            pass: false
          };
        }
      }
    };
  }
}

module.exports = ConfigTestHelpers;